package com.audaque.springboot.foshanupload.web.esdatademo.controller;

import com.audaque.springboot.foshanupload.web.esdatademo.model.pojo.Blog;
import com.audaque.springboot.foshanupload.web.esdatademo.model.pojo.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.IdsQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.ParsedMax;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.*;

@Api(tags = "增删改查（RestTemplate方式）")
@RestController
@RequestMapping("restTemplate")
public class RestTemplateController {

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    /**
     * {
     * "user" : {
     * "mappings" : {
     * "properties" : {
     * "age" : {
     * "type" : "long"
     * },
     * "children" : {
     * "type" : "nested",
     * "properties" : {
     * "age" : {
     * "type" : "long"
     * },
     * "first" : {
     * "type" : "text",
     * "fields" : {
     * "keyword" : {
     * "type" : "keyword",
     * "ignore_above" : 256
     * }
     * }
     * },
     * "id" : {
     * "type" : "text",
     * "fields" : {
     * "keyword" : {
     * "type" : "keyword",
     * "ignore_above" : 256
     * }
     * }
     * },
     * "parentId" : {
     * "type" : "text",
     * "fields" : {
     * "keyword" : {
     * "type" : "keyword",
     * "ignore_above" : 256
     * }
     * }
     * },
     * "second" : {
     * "type" : "text",
     * "fields" : {
     * "keyword" : {
     * "type" : "keyword",
     * "ignore_above" : 256
     * }
     * }
     * }
     * }
     * },
     * "first" : {
     * "type" : "text",
     * "fields" : {
     * "keyword" : {
     * "type" : "keyword",
     * "ignore_above" : 256
     * }
     * }
     * },
     * "parentId" : {
     * "type" : "text",
     * "fields" : {
     * "keyword" : {
     * "type" : "keyword",
     * "ignore_above" : 256
     * }
     * }
     * },
     * "second" : {
     * "type" : "text",
     * "fields" : {
     * "keyword" : {
     * "type" : "keyword",
     * "ignore_above" : 256
     * }
     * }
     * }
     * }
     * }
     * }
     * }
     *
     * @return
     */
    @ApiOperation("嵌套聚合查询")
    @GetMapping("nestedAggregation")
    public Page<User> nestedAggregation() {
        PageRequest pageRequest = PageRequest.of(0, 10);
        NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must().add(QueryBuilders.matchQuery("age", 23));
        boolQueryBuilder.must().add(QueryBuilders.nestedQuery("children", QueryBuilders.matchQuery("children.first", "John"), ScoreMode.Max));
        query.withQuery(boolQueryBuilder).withPageable(pageRequest);

        //reason=Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead.
        AbstractAggregationBuilder aggregationBuilder = AggregationBuilders.nested("children", "children")
                .subAggregation(AggregationBuilders.filter("children", QueryBuilders.termQuery("children.parentId.keyword", "1"))
                        .subAggregation(AggregationBuilders.terms("children_parentId")
                                .field("children.parentId.keyword")
                                .subAggregation(AggregationBuilders.terms("children_first")
                                        .field("children.first.keyword"))
                                .subAggregation(AggregationBuilders.terms("children_second")
                                        .field("children.second.keyword"))));
        query.addAggregation(aggregationBuilder);

        NativeSearchQuery build = query.build();
        String s = build.toString();
        SearchHits<User> searchHits = elasticsearchRestTemplate.search(build, User.class);
        List<User> users = new ArrayList<>();
        for (SearchHit<User> searchHit : searchHits) {
            users.add(searchHit.getContent());
        }
        return new PageImpl<User>(users, pageRequest, searchHits.getTotalHits());
    }


    @ApiOperation("子聚合")
    @GetMapping("subAggregation")
    public Map<String, Map<String, Object>> subAggregation() {
        NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.matchQuery("title", "java"));
        query.withQuery(boolQueryBuilder);
        query.addAggregation(AggregationBuilders.terms("per_count").field("author.keyword")
                .subAggregation(AggregationBuilders.max("latest_create_time").field("createTime"))
        );
        // 不需要获取source结果集，在aggregation里可以获取结果
        query.withSourceFilter(new FetchSourceFilterBuilder().build());
        SearchHits<Blog> searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class);
        Aggregations aggregations = searchHits.getAggregations();
        assert aggregations != null;
        //因为结果为字符串类型 所以用ParsedStringTerms。其他还有ParsedLongTerms、ParsedDoubleTerms等
        ParsedStringTerms per_count = aggregations.get("per_count");
        Map<String, Map<String, Object>> map = new HashMap<>();
        for (Terms.Bucket bucket : per_count.getBuckets()) {
            Map<String, Object> objectMap = new HashMap<>();
            objectMap.put("docCount", bucket.getDocCount());
            ParsedMax parsedMax = bucket.getAggregations().get("latest_create_time");
            objectMap.put("latestCreateTime", parsedMax.getValueAsString());
            map.put(bucket.getKeyAsString(), objectMap);
        }
        return map;
    }

    @ApiOperation("聚合")
    @GetMapping("aggregation")
    public Map<String, Long> aggregation() {
        NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.matchQuery("title", "java"));
        query.withQuery(boolQueryBuilder);
        // 作为聚合的字段不能是text类型。所以，author的mapping要有keyword，且通过author.keyword聚合。
        query.addAggregation(AggregationBuilders.terms("per_count").field("author.keyword"));
        // 不需要获取source结果集，在aggregation里可以获取结果
        query.withSourceFilter(new FetchSourceFilterBuilder().build());
        SearchHits<Blog> searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class);
        Aggregations aggregations = searchHits.getAggregations();
        assert aggregations != null;
        //因为结果为字符串类型 所以用ParsedStringTerms。其他还有ParsedLongTerms、ParsedDoubleTerms等
        ParsedStringTerms per_count = aggregations.get("per_count");
        Map<String, Long> map = new HashMap<>();
        for (Terms.Bucket bucket : per_count.getBuckets()) {
            map.put(bucket.getKeyAsString(), bucket.getDocCount());
        }
        return map;
    }


    @ApiOperation("去重")
    @GetMapping("collapse")
    public List<Blog> collapse(String title, String author) {
        PageRequest pageRequest = PageRequest.of(0, 2);
        NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if (StringUtils.isNotBlank(title)) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", title));
        }
        if (StringUtils.isNotBlank(author)) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("author", author));
        }
        query.withQuery(boolQueryBuilder);
        query.withPageable(pageRequest);
        query.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC));
        // 去重的字段不能是text类型。所以，author的mapping要有keyword，且通过author.keyword去重。
        query.withCollapseField("author.keyword");
        //query.withCollapseField("category");
        SearchHits<Blog> searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class);
        List<Blog> blogs = new ArrayList<>();
        for (SearchHit<Blog> searchHit : searchHits) {
            blogs.add(searchHit.getContent());
        }
        return blogs;
    }


    @ApiOperation("分页排序")
    @GetMapping("pageAndSort")
    public Page<Blog> pageAndSort(String title, String author) {
        PageRequest pageRequest = PageRequest.of(0, 2);
        NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if (StringUtils.isNotBlank(title)) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", title));
        }
        if (StringUtils.isNotBlank(author)) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("author", author));
        }
        query.withQuery(boolQueryBuilder);
        query.withPageable(pageRequest);
        query.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC));
        SearchHits<Blog> searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class);
        List<Blog> blogs = new ArrayList<>();
        for (SearchHit<Blog> searchHit : searchHits) {
            blogs.add(searchHit.getContent());
        }
        return new PageImpl<Blog>(blogs, pageRequest, searchHits.getTotalHits());
    }

    @ApiOperation("单体查询")
    @GetMapping("one")
    public List<Blog> one(String title, String content) {
        NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if (StringUtils.isNotBlank(title)) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", title));
        }
        if (StringUtils.isNotBlank(content)) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("content", content));
        }
        query.withQuery(boolQueryBuilder);
        SearchHits<Blog> searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class);
        List<Blog> blogs = new ArrayList<>();
        for (SearchHit<Blog> searchHit : searchHits) {
            blogs.add(searchHit.getContent());
        }
        return blogs;
    }


    @ApiOperation("时间区间查询")
    @PostMapping("selectByTimeFromTo")
    public List<Blog> selectByTimeFromTo(String begin, String end) {
        List<Blog> itemVOS = new ArrayList<>();

        try {
            NativeSearchQuery query = new NativeSearchQueryBuilder().withSort(SortBuilders.fieldSort("time").order(SortOrder.DESC))
                    .withFilter(QueryBuilders.rangeQuery("time").timeZone("+08:00").format("yyyy-MM-dd HH:mm:ss").gt(begin).lt(end))
                    .build();

            SearchHits<Blog> search = elasticsearchRestTemplate.search(query, Blog.class, IndexCoordinates.of("blog"));

            search.forEach((hits) -> itemVOS.add(hits.getContent()));
        } catch (Exception e) {
            e.printStackTrace();
        }

        return itemVOS;
    }


    @ApiOperation("排序查询")
    @PostMapping("selectByTimeDesc")
    public List<Blog> selectByTimeDesc() {
        List<Blog> itemVOS = new ArrayList<>();

        try {
            NativeSearchQuery query = new NativeSearchQueryBuilder().withSort(SortBuilders.fieldSort("time").order(SortOrder.DESC))
                    .build();

            SearchHits<Blog> search = elasticsearchRestTemplate.search(query, Blog.class, IndexCoordinates.of("blog"));

            search.forEach((hits) -> itemVOS.add(hits.getContent()));
        } catch (Exception e) {
            e.printStackTrace();
        }

        return itemVOS;
    }


    @ApiOperation("分页查询")
    @PostMapping("selectByPage")
    public List<Blog> selectByPage(int page, int size) {

        List<Blog> itemVOS = new ArrayList<>();

        try {
            NativeSearchQuery query = new NativeSearchQueryBuilder().withPageable(PageRequest.of(page - 1, size))
                    .build();

            SearchHits<Blog> search = elasticsearchRestTemplate.search(query, Blog.class, IndexCoordinates.of("blog"));

            search.forEach((hits) -> itemVOS.add(hits.getContent()));
        } catch (Exception e) {
            e.printStackTrace();
        }

        return itemVOS;
    }


    @ApiOperation("高亮查询")
    @PostMapping("searchHighlight")
    public List<Blog> searchHighlight(String keyword) {
        ArrayList<Blog> itemVOS = new ArrayList<>();

        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("requestBody", keyword);

        //高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.requireFieldMatch(false);//多个高亮关闭
        highlightBuilder.preTags("<span style='color:red'>");
        highlightBuilder.postTags("</span>");


        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(matchQueryBuilder)
                .build();
        query.setHighlightQuery(new HighlightQuery(highlightBuilder));

        SearchHits<Blog> search = elasticsearchRestTemplate.search(query, Blog.class, IndexCoordinates.of("blog"));

        for (SearchHit<Blog> searchHit : search) {
            Blog content = searchHit.getContent();
            //将高亮的字段取出来
            List<String> requestBody = searchHit.getHighlightField("requestBody");
            String highText = "";
            for (String s : requestBody) {
                highText = highText += s;
            }
            //重新对字段赋值
            content.setTitle(highText);
            itemVOS.add(content);
        }
        return itemVOS;
    }


    /**
     * 根据id查询
     *
     * @param id
     * @return
     */

    @ApiOperation("查看单个文档")
    @GetMapping("selectAllById")
    public Blog selectAllById(String id) {

        IdsQueryBuilder idsQueryBuilder = QueryBuilders.idsQuery();
        idsQueryBuilder.addIds(id);

        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(idsQueryBuilder)
                .build();

        SearchHit<Blog> itemSearchHit = elasticsearchRestTemplate.searchOne(query, Blog.class, IndexCoordinates.of("blog"));
        Blog content = itemSearchHit.getContent();
        return content;
    }


    @ApiOperation("查看单个文档")
    @GetMapping("findById")
    public Blog findById(Long id) {
        return elasticsearchRestTemplate.get(
                id.toString(), Blog.class, IndexCoordinates.of("blog"));
    }

    @ApiOperation("创建索引并推送映射")
    @PostMapping("createIndexAndPushMapping")
    public boolean createIndexAndPushMapping() {
        if(!elasticsearchRestTemplate.indexExists(Blog.class)){
            IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Blog.class);
            //创建索引
            boolean a = indexOperations.create();
            if (a) {
                //生成映射
                Document mapping = indexOperations.createMapping();
                //推送映射
                boolean b = indexOperations.putMapping(mapping);
                return b;
            } else {
                return a;
            }
        }
        return true;

    }


    @ApiOperation("添加单个文档")
    @PostMapping("addDocument")
    public Blog addDocument() {
        Long id = 1L;
        Blog blog = new Blog();
        blog.setBlogId(id);
        blog.setTitle("Spring Data ElasticSearch学习教程" + id);
        blog.setContent("这是添加单个文档的实例" + id);
        blog.setAuthor("Tony");
        blog.setCategory("ElasticSearch");
        blog.setCreateTime(new Date());
        blog.setStatus(1);
        blog.setSerialNum(id.toString());
        return elasticsearchRestTemplate.save(blog);
    }

    @ApiOperation("添加多个文档")
    @PostMapping("addDocuments")
    public Object addDocuments(Integer count) {
        List<Blog> blogs = new ArrayList<>();
        for (int i = 1; i <= count; i++) {
            Long id = (long) i;
            Blog blog = new Blog();
            blog.setBlogId(id);
            blog.setTitle("Spring Data ElasticSearch学习教程" + id);
            blog.setContent("这是添加单个文档的实例" + id);
            blog.setAuthor("Tony");
            blog.setCategory("ElasticSearch");
            blog.setCreateTime(new Date());
            blog.setStatus(1);
            blog.setSerialNum(id.toString());
            blogs.add(blog);
        }
        return elasticsearchRestTemplate.save(blogs);
    }

    /**
     * 跟新增是同一个方法。若id已存在，则修改。
     * 无法只修改某个字段，只能覆盖所有字段。若某个字段没有值，则会写入null。
     *
     * @return 成功写入的数据
     */
    @ApiOperation("修改单个文档数据")
    @PostMapping("editDocument")
    public Blog editDocument() {
        Long id = 1L;
        Blog blog = new Blog();
        blog.setBlogId(id);
        blog.setTitle("Spring Data ElasticSearch学习教程" + id);
        blog.setContent("这是修改单个文档的实例" + id);
        return elasticsearchRestTemplate.save(blog);
    }

    @ApiOperation("修改单个文档部分数据")
    @PostMapping("editDocumentPart")
    public UpdateResponse editDocumentPart() {
        long id = 1L;
        Document document = Document.create();
        document.put("title", "修改后的标题" + id);
        document.put("content", "修改后的内容" + id);
        UpdateQuery updateQuery = UpdateQuery.builder(Long.toString(id))
                .withDocument(document)
                .build();
        UpdateResponse response = elasticsearchRestTemplate.update(updateQuery, IndexCoordinates.of("blog"));
        return response;
    }

    @ApiOperation("修改多个文档部分数据")
    @PostMapping("editDocumentsPart")
    public void editDocumentsPart(int count) {
        List<UpdateQuery> updateQueryList = new ArrayList<>();
        for (int i = 1; i <= count; i++) {
            long id = (long) i;
            Document document = Document.create();
            document.put("title", "修改后的标题" + id);
            document.put("content", "修改后的内容" + id);
            UpdateQuery updateQuery = UpdateQuery.builder(Long.toString(id))
                    .withDocument(document)
                    .build();
            updateQueryList.add(updateQuery);
        }
        elasticsearchRestTemplate.bulkUpdate(updateQueryList, IndexCoordinates.of("blog"));
    }

    @ApiOperation("删除单个文档(根据id)")
    @PostMapping("deleteDocumentById")
    public String deleteDocumentById(Long id) {
        return elasticsearchRestTemplate.delete(id.toString(), Blog.class);
    }

    @ApiOperation("删除单个文档(根据条件)")
    @PostMapping("deleteDocumentByQuery")
    public void deleteDocumentByQuery(String title) {
        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.matchQuery("title", title))
                .build();
        elasticsearchRestTemplate.delete(nativeSearchQuery, Blog.class, IndexCoordinates.of("blog"));
    }

    @ApiOperation("删除所有文档")
    @PostMapping("deleteDocumentAll")
    public void deleteDocumentAll() {
        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.matchAllQuery())
                .build();
        elasticsearchRestTemplate.delete(nativeSearchQuery, Blog.class, IndexCoordinates.of("blog"));
    }


}
